Back to ELF
Pipeline, November/December 1995, vol.6, no.6
Copyright © 1995 Silicon Graphics
Controlling a Program's Layout with ELSPEC


Settings within the linker, ld(1), control the layout of text and
data sections within a program. Occasionally it is desirable to
override the default layout of these sections.  Prior to the 6.0.2
compiler release, ld provided several command line options (-T, -D,
-B, and several options beginning with -X) to override the default
layout.  Because these options can be difficult to use, the 6.0.2
linker introduces a more flexible solution for controlling program
layout: the ELF Layout Specification language, ELSPEC.

The elspec(5) manual page contains a complete ELSPEC language definition as well as sample ELSPEC files. This article briefly describes how programmers can use ELSPEC. It provides examples in both C and FORTRAN.

Embedded systems and multi-process applications typically require special layout of text and data. Multi-process applications often require that some data be local to each thread. Embedded systems require data and instructions to reside within certain address ranges and typically have very specific alignment requirements. All of these needs are now addressed through the common interface of ELSPEC.

Examples

The remainder of this article provides examples in both C and FORTRAN. To see a sample ELSPEC file for a particular application, add the options -Wl,-elsmap to the compile line, or add the option -elsmap to the linker line.

Specifying the Virtual Address of a FORTRAN Common Block

This example illustrates how to place a FORTRAN common block at virtual address 0x50040000. To see the default addresses that the linker chose for the variables foo1 and foo2 in the common block aa, compile and execute the program as shown below:

% f77 addr1.f -o addr1
% ./addr1

100120D0
100130D0

To change the addresses of foo1 and foo2, include the ELSPEC file eladdr1 on the compile line, and rerun the program.

% f77 addr1.f -o addr1 -Wl,-elspec,eladdr1
% ./addr1

50040000
50041000

c file addr1.f 
	program addr1
	integer foo1(1024) 
	integer foo2(1024) 
	common /aa/ foo1,foo2
c address of 1st common aa entry
	write(6,10)%loc(foo1) 
c address of 2nd common aa entry
	write(6,10)%loc(foo2) 	
10 	format(x,z) 
	stop 
	end

# start file eladdr1
	beginseg 
		name text 
		segtype LOAD 
		segflags R X 
		segalign 0x1000 
		contents default 
	endseg
	beginseg 
		name data 
		segtype LOAD 
		segflags R W 
		segalign 0x1000 
		contents default 
	endseg 

# create an additional data segment for the 
# section .mybss0. Set the address for the 
# common block aa.

	beginseg 
		segtype LOAD 
		segflags R W 
		vaddr 0x50040000 
		contents 
		beginscn .mybss0 
			scntype NOBITS 
			scnflags ALLOC WRITE 
			sym aa_ 
		endscn 
	endseg 
# end file eladdr1
Specifying the Virtual Address for the Text and Data Sections

This example illustrates how to determine the default addresses for text and data sections and how to specify new addresses. The default virtual addresses of the text and data sections under the 6.0.2 compilers are different from those of the compilers available with IRIX 4.0.x and IRIX 5.x. To display the default virtual addresses, compile and execute the example C program as shown below:

% cc -DBUG compact1.c -o compact1
% ./compact1

Start of program text = 0x10000c50
Start of program data = 0x10011000

To change the virtual addresses, compile the program using the ELSPEC file elcompact1 and execute it. Note that there is a bug in the 6.0.2 C compiler that causes the variable _ftext to be undefined when using an ELSPEC file. Because of this bug, the example displays the virtual address of the entry point symbol __start, which is close to the text origin. Most programs should not encounter this bug. However, in those cases where this bug occurs, ELSPEC cannot be used to override the default virtual addresses of the text section. This bug is expected to be resolved in a future release of the linker.

% cc -DBUG compact1.c -o compact1 -Wl,-elspec, elcompact1
% ./compact1

Start of program text = 0x4001f0
Start of program data = 0x10000000

/* file compact1.c */ 
#include <stdio.h> 
#include <stdlib.h>
#ifdef BUG 
extern int __start[]; 
#else 
extern int _ftext[]; 
#endif
extern int _fdata[]; 
#define SIZE 4096 
int array[SIZE];
void main(void) { 
#ifdef BUG
	printf("Start of program text = 0x%lx\n",(unsigned long)__start);
#else
	printf("Start of program text = 0x%lx\n",(unsigned long)_ftext);
#endif
	printf("Start of program data = 0x%lx\n",(unsigned long)_fdata);
	exit(0); 
}

# start file elcompact1
	beginseg 
		name text 
# set text's origin 
		segtype LOAD 
		vaddr 0x400000 
		segflags R X 
		segalign 0x1000 
		contents default 
	endseg

	beginseg 
		name data 
		segtype LOAD 
# set data's origin 
		vaddr 0x10000000 
		segflags R W X 
		segalign 0x1000 
		contents default 
	endseg

	beginseg 
		name noload 
		segtype noload 
		contents default 
	endseg 
# end file elcompact1
Multiple Copies of Data Structures

Multi-process applications often need each process to have a separate copy of some data structures. To illustrate the issue of multiple processes not maintaining their own data structures, compile and execute the program sproc1 as shown below. The intent of sproc1 is to sproc(2) three processes and have each process fill its own array with a different number. Thus, the first process (process 4691) should fill its entire array with zeros. The second process (process 4692) should write ones to its copy of the array, while the third process (process 4693) should fill its copy with the number two.

% cc sproc1.c -o sproc1 % ./sproc1
pid 4692, array[0] = 1
pid 4691, array[0] = 0
pid 4692, array[1] = 0
pid 4691, array[1] = 0
pid 4692, array[2] = 0
pid 4691, array[2] = 0
pid 4693, array[0] = 2
pid 4693, array[1] = 2
pid 4693, array[2] = 2

However, because all three processes read and write to the exact same array foo1, incorrect results are produced.

The ELSPEC file elsproc1 listed below can be used to circumvent this behavior. It forces each process to create its own local copy of the array foo1. Compile and execute the sample program sproc1 as shown below. Note that each process (4709, 4710, and 4711) now correctly writes the values 0, 1, or 2 to all three elements of their copy of the array foo1.

% cc sproc1.c -o sproc1 -common -Wl,-elspec, elsproc1
% ./sproc1
pid 4709, array[0] = 0
pid 4710, array[0] = 1
pid 4709, array[1] = 0
pid 4710, array[1] = 1
pid 4709, array[2] = 0
pid 4710, array[2] = 1
pid 4711, array[0] = 2
pid 4711, array[1] = 2
pid 4711, array[2] = 2

/* file sproc1.c */ 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/prctl.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h>
#define SIZE 3 
int foo1[SIZE]; 
volatile int waiting = 1;

void r1(void *val) {
	int i,loc=*(int*)val; 
	pid_t pid = getpid(); 
	while(waiting);
	for(i=0;i<SIZE;i++) foo1[i] = loc;
	for(i=0;i<SIZE;i++) { 
		printf("pid %d, foo1[%d] = %d\n",pid,i,foo1[i]); 
	} 
}

void main(void) {
	int i; 
	int d[SIZE] = {0,1,2};
	for(i=0;i<SIZE;i++) { 
		if (-1==sproc(r1,PR_SALL,(void*)&d[i])) { 
			printf("sproc failed\n"); 
			if (kill(0,9)==-1) 
				printf("kill failed\n"); 
		} 
	}
	sginap(50); 
	waiting = 0; 
	sginap(200);
	exit(0); 
}

# start file elsproc1
	beginseg 
		segtype LOAD 
		segflags R X 
		vaddr 0x10000000 
		segalign 0x1000 
		contents default 
	endseg

	beginseg 
		segtype LOAD 
		segflags R W 
		segalign 0x1000 
		contents default 
	endseg

	beginseg 
		segtype LOAD 
# The use of L in segflags field is key. This 
# allows each process to get a separate copy of 
# the array foo1. 
		segflags R W L 
		contents 
		beginscn .mybss 
			scntype NOBITS 
			scnflags ALLOC WRITE 
			sym foo1 
		endscn 
	endseg 
# end file elsproc1